// as soon as possible
// micro: Promise.resolve().then() - process.nextTick[Node] - MutationObserver
// macro: SetImmediate[IE] - MessageChannel - setTimeout
// references: RSVP.js | es6-promise | immediate

import { isNode, isSupport } from "./env-detect";

/**
 * 在队列中使用2个数组位表示一个回调: (callback, arguments)
 */
const JOB_WIDTH = 2;

/**
 * 利用 MutationObserver 微任务
 * @param {Function} callback
 * @returns trigger
 */
function useMutationObserver(callback) {
  let counter = 1;
  const textNode = document.createTextNode(String(counter));
  const observeOptions = { characterData: true };

  const ob = new MutationObserver(callback);
  ob.observe(textNode, observeOptions);

  return () => {
    counter = (counter + 1) % 2;
    textNode.data = counter;
  };
}

/**
 * 利用 MessageChannel 宏任务
 * @param {Function} callback
 * @returns trigger
 */
function useMessageChannel(callback) {
  const chanel = new MessageChannel();
  chanel.port1.onmessage = callback;

  return () => {
    chanel.port2.postMessage(0);
  };
}

/**
 * 优先级最低的宏任务
 * @param {Function} callback
 * @returns trigger
 */
function useSetTimeout(callback) {
  const globalSetTimeout = setTimeout;

  return () => {
    globalSetTimeout(callback, 0);
  };
}

/**
 * 根据支持性选择合适的调度器
 * @param {Function} callback
 * @returns trigger
 */
const getScheduler = (callback) => {
  if (isNode) return () => process.nextTick(callback);
  if (isSupport(MutationObserver)) return useMutationObserver(callback);
  if (isSupport(MessageChannel)) return useMessageChannel(callback);
  return useSetTimeout(callback);
};

/* 调度器 */
const scheduler = getScheduler(flush);

/* Job Queue */
const queue = [];
let size = 0;

/* Clear Queue */
function flush() {
  for (let i = 0; i < queue.length; i += JOB_WIDTH) {
    const callback = queue[i];
    const args = queue[i + 1];
    callback(args);
  }

  size = 0;
}

/* Enqueue */
export function asap(callback, args) {
  queue.push(callback, args);
  size += JOB_WIDTH;
  // 上升沿触发，记录从0到非0的change
  if (JOB_WIDTH === size) {
    scheduler(); // This is asynchronous. Will reset `size` to 0.
  }
}
